#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File     : vit.py
# @Time     : 2024/8/21 15:05
import cv2
import torch
import torch.nn as nn
import numpy as np
import torchvision.transforms.functional as F
from PIL import Image
from einops import rearrange, repeat
from einops.layers.torch import Rearrange
from torchvision import transforms


def pair(t):
    return t if isinstance(t, tuple) else (t, t)


class Residual(nn.Module):
    def __init__(self, fn):
        super(Residual, self).__init__()
        self.fn = fn

    def forward(self, x, **kwargs):
        return self.fn(x, **kwargs) + x


class PreNorm(nn.Module):
    def __init__(self, dim, fn):
        super(PreNorm, self).__init__()
        self.norm = nn.LayerNorm(dim)
        self.fn = fn

    def forward(self, x, **kwargs):
        return self.fn(self.norm(x), **kwargs)


class FeedForward(nn.Module):
    def __init__(self, dim, hidden_dim, dropout=0.):
        super(FeedForward, self).__init__()
        self.net = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Linear(dim, hidden_dim),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim, dim),
            nn.Dropout(dropout)
        )

    def forward(self, x):
        return self.net(x)


class Attention(nn.Module):
    def __init__(self, dim, heads=8, dim_head=64, dropout=0.):
        super(Attention, self).__init__()
        inner_dim = heads * dim_head

        # True
        project_out = not (heads == 1 and dim_head == dim)

        self.heads = heads
        self.scale = dim ** -0.5

        self.norm = nn.LayerNorm(dim)
        self.attend = nn.Softmax(dim=-1)
        self.dropout = nn.Dropout(dropout)

        self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)
        self.to_out = nn.Sequential(
            nn.Linear(inner_dim, dim),
            nn.Dropout(dropout)
        ) if project_out else nn.Identity()

    def forward(self, x):
        x = self.norm(x)

        # b, 65, 1024, heads = 8
        # b, n, _, h = *x.shape, self.heads

        # self.to_qkv(x): b, 65, 64*8*3
        # qkv: b, 65, 64*8
        # chunk 函数用于将张量沿着指定维度拆分成多个块。
        # 在这个例子中，我们将张量沿着最后一个维度 dim=-1 拆分为 3 个块，
        # 因为我们要从这个维度生成查询（Q）、键（K）、值（V）
        qkv = self.to_qkv(x).chunk(3, dim=-1)
        # b, 65, 64*8 --> b, 8, 65, 64
        q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h=self.heads), qkv)

        # dots: b, 8, 65, 65
        # i 和 j 是序列位置（即查询和键的位置）
        dots = torch.einsum('bhid,bhjd->bhij', q, k) * self.scale

        # mask_value = -torch.finfo(dots.dtype).max
        #
        # if mask is not None:
        #     mask = F.pad(mask.flatten(1), (1, 0), value=True)
        #     assert mask.shape[-1] == dots.shape[-1], "mask has incorrect dimensions"
        #     mask = mask[:, None, :] * mask[:, :, None]
        #     dots.masked_fill_(~mask, mask_value)
        #     del mask

        attn = self.dropout(self.attend(dots))
        # attn: b, 8, 65, 65
        # attn = dots.softmax(dim=-1)

        # out: b, 8, 65, 65 * b, 8, 65, 64 = b, 8, 65, 64
        out = torch.einsum('bhij,bhjd->bhid', attn, v)

        # out: b, 8, 65, 64 --> b, 65, 8*64
        out = rearrange(out, 'b h n d -> b n (h d)')

        # out: b, 65, 512 --> b, 65, 1024
        out = self.to_out(out)
        return out


class Transformer(nn.Module):
    def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout):
        super(Transformer, self).__init__()
        self.norm = nn.LayerNorm(dim)
        self.layers = nn.ModuleList([])
        for _ in range(depth):
            self.layers.append(nn.ModuleList([
                Attention(dim, heads=heads, dim_head=dim_head, dropout=dropout),
                FeedForward(dim, mlp_dim, dropout=dropout)
            ]))

    def forward(self, x):
        for attn, ff in self.layers:
            x = attn(x) + x
            x = ff(x) + x

        return self.norm(x)


class ViT(nn.Module):
    def __init__(self, *, image_size, patch_size, num_classes, dim, depth, heads, mlp_dim, pool='cls', channels=3,
                 dim_head=64, dropout=0., emb_dropout=0.):
        super().__init__()
        image_height, image_width = pair(image_size)
        patch_height, patch_width = pair(patch_size)
        assert image_height % patch_height == 0 and image_width % patch_width == 0, 'Image dimensions must be divisible by the patch size.'

        num_patches = (image_height // patch_height) * (image_width // patch_width)
        print(f'num_patches:{num_patches}')
        patch_dim = channels * patch_height * patch_width
        assert pool in {'cls', 'mean'}, 'pool type must be either cls (cls token) or mean (mean pooling)'

        self.to_patch_embedding = nn.Sequential(
            Rearrange('b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=patch_height, p2=patch_width),
            nn.LayerNorm(patch_dim),
            nn.Linear(patch_dim, dim),
            nn.LayerNorm(dim),
        )

        self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
        self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
        self.dropout = nn.Dropout(emb_dropout)
        self.transformer = Transformer(dim, depth, heads, dim_head, mlp_dim, dropout=dropout)
        self.pool = pool
        self.to_latent = nn.Identity()

        self.mlp_head = nn.Linear(dim, num_classes)

    def forward(self, img):
        x = self.to_patch_embedding(img)
        print(f'x.shape : {x.shape}')
        b, n, _ = x.shape

        cls_tokens = repeat(self.cls_token, '1 1 d -> b 1 d', b=b)
        x = torch.cat((cls_tokens, x), dim=1)
        print(f'torch.cat((cls_tokens, x), dim=1) : {x.shape}')

        # print(f'self.pos_embedding[:, :(n + 1)]:{self.pos_embedding[:, :(n + 1)]}')
        x += self.pos_embedding[:, :(n + 1)]

        x = self.dropout(x)

        x = self.transformer(x)
        print(f'self.transformer(x):{x.shape}')
        print(x)

        # print(f'x[:, 0]:{x[:, 0]}, {x[:, 0].shape}')
        x = x.mean(dim=1) if self.pool == 'mean' else x[:, 0]
        print(f'x = x.mean(dim=1) if self.pool == mean else x[:, 0]:{x}, {x.shape}')

        x = self.to_latent(x)
        return self.mlp_head(x)


# img = torch.randn(1, 3, 224, 224)
img = cv2.imread("../xxx.jpg")
print(f'Original shape: {img.shape}')
# 将图像转换为 PIL 图像
img_pil = Image.fromarray(img)
# 定义转换操作
transform = transforms.Compose([
    transforms.CenterCrop(224),               # 裁剪图像的中心部分
    transforms.Resize((224, 224)),            # 确保图像的大小是 224x224
    transforms.ToTensor(),                    # 将图像转换为 Tensor 并将像素值缩放到 [0, 1]
])
# 应用转换
img_tensor = transform(img_pil)
img = img_tensor
# 添加批量维度
img_tensor = img_tensor.unsqueeze(0)
print(f'img_tensor.shape:{img_tensor.shape}')

# img_pil = F.to_pil_image(img)
# import matplotlib.pyplot as plt
# 使用 matplotlib 显示图像
# plt.imshow(img_pil)
# plt.axis('off')  # 不显示坐标轴
# plt.show()


model = ViT(image_size=224, patch_size=16, num_classes=10, dim=1024, depth=1, heads=8, mlp_dim=2048, pool='cls')
print(model)
print(f'self.mlp_head(x):{model(img_tensor)}')
